#ifndef GTL_FUNCTION_BINDING_HEADER_GUARD
#define GTL_FUNCTION_BINDING_HEADER_GUARD

#include "gtlCompilerSpecific.h"
#include "external_libs/eastl/include/eastl/vector.h"

namespace GTL
{
  namespace Functions
  {

    struct VariableVariant {
      enum Type
      {
        AT_VOID,
        AT_INT,
        AT_UINT,
        AT_FLOAT,
        AT_BOOL,
        AT_CHAR,
        AT_CHARSTAR,
        AT_CONSTCHARSTAR,
				AT_VOIDPTR,
      };

      union
      {
        int av_int;
        unsigned int av_uint;
        float av_float;
        bool av_bool;
        char av_char;
        char* av_charstar;
        const char* av_constcharstar;
				void* av_voidptr;
      };

      int type;

      VariableVariant() : type(AT_VOID), av_int(0) {}
      VariableVariant(int arg) : type(AT_INT), av_int(arg) {}
      VariableVariant(unsigned int arg) : type(AT_UINT), av_uint(arg) {}
      VariableVariant(float arg) : type(AT_FLOAT), av_float(arg) {}
			VariableVariant(void* arg) : type(AT_VOIDPTR), av_voidptr(arg) {}
			VariableVariant(const char* arg) : type(AT_CONSTCHARSTAR), av_constcharstar(arg) {}

      VariableVariant& operator=(int _int) { type= AT_INT; av_int = _int; return *this; }
      VariableVariant& operator=(unsigned int _int) { type= AT_UINT; av_uint = _int; return *this; }
      VariableVariant& operator=(float _int) { type= AT_FLOAT; av_float = _int; return *this; }
			VariableVariant& operator=(void* _int) { type= AT_VOIDPTR; av_voidptr = _int; return *this; }
			VariableVariant& operator=(const char* _int) { type= AT_CONSTCHARSTAR; av_constcharstar = _int; return *this; }

      operator int() const { return av_int; }
      operator unsigned int() const { return av_uint; }
      operator float() const { return av_float; }
			operator void*() const { return av_voidptr; }
			operator const char*() const { return av_constcharstar; }
    };

    template <typename T> struct TypeDeducer { enum { type = VariableVariant::AT_VOID }; };

		/// TODO: Zamiast name() tutaj, dac osobna funkcje zwracajaca string typu na podstawie enuma
		/// TODO: Zamiast struktury moze po prostu funkcje? bedzie prosciej po zmiennych dedukowac
    template <> struct TypeDeducer<void> { enum { type = VariableVariant::AT_VOID }; static const char* name() {return "void";} };
    template <> struct TypeDeducer<int> { enum { type = VariableVariant::AT_INT }; static const char* name() {return "int";} };
    template <> struct TypeDeducer<unsigned int> { enum { type = VariableVariant::AT_UINT }; static const char* name() {return "uint";} };
    template <> struct TypeDeducer<float> { enum { type = VariableVariant::AT_FLOAT }; static const char* name() {return "float";} };
    template <> struct TypeDeducer<bool> { enum { type = VariableVariant::AT_BOOL }; static const char* name() {return "bool";} };
    template <> struct TypeDeducer<char> { enum { type = VariableVariant::AT_CHAR }; static const char* name() {return "char";} };
    template <> struct TypeDeducer<char*> { enum { type = VariableVariant::AT_CHARSTAR }; static const char* name() {return "char*";} };
    template <> struct TypeDeducer<const char*> { enum { type = VariableVariant::AT_CONSTCHARSTAR }; static const char* name() {return "const char*";} };

    class FunctionBinding;

    typedef VariableVariant(*BindedFunctionCall)(FunctionBinding&,VariableVariant*,int);
    typedef bool(*BindedFunctionSafeCall)(FunctionBinding&,VariableVariant*,int,VariableVariant*);

    class FunctionBinding
    {
    public:
      const char* Name;
      int ReturnType;
      eastl::vector<int> ArgumentType;
      BindedFunctionCall Caller;
      BindedFunctionSafeCall SafeCaller;
      void* Function;

      /************************************************************************/
      /* Constructors                                                         */
      /************************************************************************/

      template <typename RET_TYPE>
      FunctionBinding(RET_TYPE(*func)())
      {
        Name = "";
        ReturnType = TypeDeducer<RET_TYPE>::type;
        Caller = &Caller0<RET_TYPE>::Call;
        SafeCaller = &Caller0<RET_TYPE>::CallSafe;

        Function = (void*)func;
      }

      template <typename RET_TYPE, typename ARG1_TYPE>
      FunctionBinding(RET_TYPE(*func)(ARG1_TYPE))
      {
        Name = "";
        ReturnType = TypeDeducer<RET_TYPE>::type;
        ArgumentType.push_back((int)TypeDeducer<ARG1_TYPE>::type);
        Caller = &Caller1<RET_TYPE, ARG1_TYPE>::Call;
        SafeCaller = &Caller1<RET_TYPE, ARG1_TYPE>::CallSafe;

        Function = (void*)func;
      }

      template <typename RET_TYPE, typename ARG1_TYPE, typename ARG2_TYPE>
      FunctionBinding(RET_TYPE(*func)(ARG1_TYPE,ARG2_TYPE))
      {
        Name = "";
        ReturnType = TypeDeducer<RET_TYPE>::type;
        ArgumentType.push_back((int)TypeDeducer<ARG1_TYPE>::type);
        ArgumentType.push_back((int)TypeDeducer<ARG2_TYPE>::type);
        Caller = &Caller2<RET_TYPE, ARG1_TYPE, ARG2_TYPE>::Call;
        SafeCaller = &Caller2<RET_TYPE, ARG1_TYPE, ARG2_TYPE>::CallSafe;

        Function = (void*)func;
      }

      /************************************************************************/
      /* Calls                                                                */
      /************************************************************************/

      VariableVariant Call(VariableVariant* args, int argc)
      {
        return Caller(*this, args, argc);
      }

      template <typename T>
      bool SafeCall(VariableVariant* args, int argc, T* returnValue)
      {
        if (!Function) return false;

        VariableVariant ret;
        if (SafeCaller(*this, args, argc, &ret))
        {
          *returnValue = (T)ret;
          return true;
        } else
        {
          return false;
        }
      }

      /************************************************************************/
      /* Regular calls                                                        */
      /************************************************************************/

      template <typename RET_TYPE>
      bool SafeCallRegular(RET_TYPE* returnValue)
      {
        VariableVariant ret;
        if (SafeCaller(*this, 0, 0, &ret))
        {
          *returnValue = (RET_TYPE)ret;
          return true;
        } else return false;
      }

      VariableVariant CallRegular()
      {
        return Caller(*this, 0, 0);
      }

      template <typename RET_TYPE, typename ARG1_TYPE>
      bool SafeCallRegular(RET_TYPE* returnValue, ARG1_TYPE arg1)
      {
        VariableVariant args[1]; args[0] = arg1;

        VariableVariant ret;
        if (SafeCaller(*this, args, 1, &ret))
        {
          *returnValue = (RET_TYPE)ret;
          return true;
        } else return false;
      }

      template <typename ARG1_TYPE>
      VariableVariant CallRegular(ARG1_TYPE arg1)
      {
        VariableVariant args[1]; args[0] = arg1;
        return Caller(*this, args, 1);
      }

      template <typename RET_TYPE, typename ARG1_TYPE, typename ARG2_TYPE>
      bool SafeCallRegular(RET_TYPE* returnValue, ARG1_TYPE arg1, ARG2_TYPE arg2)
      {
        VariableVariant args[2]; args[0] = arg1; args[1] = arg2;

        VariableVariant ret;
        if (SafeCaller(*this, args, 2, &ret))
        {
          *returnValue = (RET_TYPE)ret;
          return true;
        } else return false;
      }

      template <typename ARG1_TYPE, typename ARG2_TYPE>
      VariableVariant CallRegular(ARG1_TYPE arg1, ARG2_TYPE arg2)
      {
        VariableVariant args[2]; args[0] = arg1; args[1] = arg2;
        return Caller(*this, args, 2);
      }
    };

    template <typename RET_TYPE>
    struct Caller0
    {
      typedef RET_TYPE(*FT)();
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        return func();
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 0 || !returnValue) return false;

        FT func = (FT)binding.Function;
        *returnValue = func();
        return true;
      }
    };

    template <>
    struct Caller0<void>
    {
      typedef void(*FT)();
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        func();
        return VariableVariant();
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 0) return false;

        FT func = (FT)binding.Function;
        func();
        return true;
      }
    };

    template <typename RET_TYPE, typename ARG1_TYPE>
    struct Caller1
    {
      typedef RET_TYPE(*FT)(ARG1_TYPE);
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        return func((ARG1_TYPE)args[0]);
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 1 || !returnValue) return false;

        FT func = (FT)binding.Function;
        *returnValue = func((ARG1_TYPE)args[0]);
        return true;
      }
    };

    template <typename ARG1_TYPE>
    struct Caller1<void, ARG1_TYPE>
    {
      typedef void(*FT)(ARG1_TYPE);
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        func(args[0]);
        return VariableVariant();
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 1) return false;

        FT func = (FT)binding.Function;
        func((ARG1_TYPE)args[0]);
        return true;
      }
    };

    template <typename RET_TYPE, typename ARG1_TYPE, typename ARG2_TYPE>
    struct Caller2
    {
      typedef RET_TYPE(*FT)(ARG1_TYPE, ARG2_TYPE);
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        return func((ARG1_TYPE)args[0], (ARG2_TYPE)args[1]);
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 2 || !returnValue) return false;

        FT func = (FT)binding.Function;
        *returnValue = func((ARG1_TYPE)args[0], (ARG2_TYPE)args[1]);
        return true;
      }
    };

    template <typename ARG1_TYPE, typename ARG2_TYPE>
    struct Caller2<void, ARG1_TYPE, ARG2_TYPE>
    {
      typedef void(*FT)(ARG1_TYPE, ARG2_TYPE);
      static VariableVariant Call(FunctionBinding& binding, VariableVariant* args, int argc)
      {
        FT func = (FT)binding.Function;
        func(args[0], args[1]);
        return VariableVariant();
      }
      static bool CallSafe(FunctionBinding& binding, VariableVariant* args, int argc, VariableVariant* returnValue)
      {
        if (!binding.Function || argc != 2) return false;

        FT func = (FT)binding.Function;
        func((ARG1_TYPE)args[0], (ARG2_TYPE)args[1]);
        return true;
      }
    };

    template <typename RET_TYPE>
    FunctionBinding CreateBindingNamed(const char* name, RET_TYPE(*func)())
    {
      FunctionBinding cb;
      cb.Name = name;
      cb.ReturnType = TypeDeducer<RET_TYPE>::type;
      cb.Caller = &Caller0<RET_TYPE>::Call;
      cb.SafeCaller = &Caller0<RET_TYPE>::CallSafe;

      cb.Function = (void*)func;

      return cb;
    }

    template <typename RET_TYPE, typename ARG1_TYPE>
    FunctionBinding CreateBindingNamed(const char* name, RET_TYPE(*func)(ARG1_TYPE))
    {
      FunctionBinding cb;
      cb.Name = name;
      cb.ReturnType = TypeDeducer<RET_TYPE>::type;
      cb.ArgumentType.push_back((int)TypeDeducer<ARG1_TYPE>::type);
      cb.Caller = &Caller1<RET_TYPE, ARG1_TYPE>::Call;
      cb.SafeCaller = &Caller1<RET_TYPE, ARG1_TYPE>::CallSafe;

      cb.Function = (void*)func;

      return cb;
    }

    template <typename RET_TYPE, typename ARG1_TYPE, typename ARG2_TYPE>
    FunctionBinding CreateBindingNamed(const char* name, RET_TYPE(*func)(ARG1_TYPE,ARG2_TYPE))
    {
      FunctionBinding cb;
      cb.Name = name;
      cb.ReturnType = TypeDeducer<RET_TYPE>::type;
      cb.ArgumentType.push_back((int)TypeDeducer<ARG1_TYPE>::type);
      cb.ArgumentType.push_back((int)TypeDeducer<ARG2_TYPE>::type);
      cb.Caller = &Caller2<RET_TYPE, ARG1_TYPE, ARG2_TYPE>::Call;
      cb.SafeCaller = &Caller2<RET_TYPE, ARG1_TYPE, ARG2_TYPE>::CallSafe;

      cb.Function = (void*)func;

      return cb;
    }

  }
}

#endif